package ar.edu.itba.it.cg.grupo01.impl.shape;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.vecmath.Point2f;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import ar.edu.itba.it.cg.grupo01.api.Intersection;
import ar.edu.itba.it.cg.grupo01.api.Ray;
import ar.edu.itba.it.cg.grupo01.api.shape.Shape;
import ar.edu.itba.it.cg.grupo01.impl.IntersectionFactory;
import ar.edu.itba.it.cg.grupo01.impl.shape.shader.Shader;

public class TriangleMesh extends AbstractShape implements Shape {

	public List<Triangle> triangles = new ArrayList<Triangle>();

	public TriangleMesh(IntersectionFactory intersectionFactory) {
		this(new ArrayList<Triangle>(), intersectionFactory);
	}

	public TriangleMesh(List<Triangle> triangles,
			IntersectionFactory intersectionFactory) {
		super(intersectionFactory);
		this.triangles = triangles;
	}

	@Override
	public boolean containedInAABB(AABB box) {

		for (Triangle triangle : triangles) {
			if (triangle.containedInAABB(box)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public Point3f getMaxXYZ() {

		Point3f max = new Point3f(Float.MIN_VALUE, Float.MIN_VALUE,
				Float.MIN_VALUE);
		for (Triangle triangle : this.triangles) {
			for (Point3f vertex : triangle.getVertices()) {
				if (vertex.x >= max.x)
					max.x = vertex.x;
				if (vertex.y >= max.y)
					max.y = vertex.y;
				if (vertex.z >= max.z)
					max.z = vertex.z;
			}
		}
		return max;
	}

	@Override
	public Point3f getMinXYZ() {

		Point3f min = new Point3f(Float.MAX_VALUE, Float.MAX_VALUE,
				Float.MAX_VALUE);
		for (Triangle triangle : this.triangles) {
			for (Point3f vertex : triangle.getVertices()) {
				if (vertex.x < min.x)
					min.x = vertex.x;
				if (vertex.y < min.y)
					min.y = vertex.y;
				if (vertex.z < min.z)
					min.z = vertex.z;
			}
		}
		return min;
	}

	@Override
	public Shape applyTransformation() {

		for (Triangle triangle : triangles) {
			Collection<Point3f> vertices = triangle.getVertices();

			for (Point3f vertex : vertices) {
				transformations.transform(vertex);
			}

			triangle.setShader(triangle.getShader());
		}

		Shape shape = new TriangleMesh(triangles, intersectionFactory);

		return shape;
	}

	@Override
	public Shape getTransformedShape() {

		List<Triangle> newtriangles = new ArrayList<Triangle>();
		for (Triangle triangle : this.triangles) {
			triangle.setTransformation(transformations);
			triangle.applyTransformation();
			newtriangles.add((Triangle) triangle.getTransformedShape());
		}
		return new TriangleMesh(newtriangles, intersectionFactory);
	}

	@Override
	public Collection<Point3f> getVertices() {

		Set<Point3f> vertices = new HashSet<Point3f>();
		for (Triangle triangle : this.triangles) {
			vertices.addAll(triangle.getVertices());
		}
		return vertices;
	}

	@Override
	public Intersection<? extends Shape> intersects(Ray ray) {

		Set<Intersection<? extends Shape>> intersections = new HashSet<Intersection<? extends Shape>>();
		for (Triangle triangle : this.triangles) {
			intersections.add(triangle.intersects(ray));
		}

		return intersectionFactory.createIntersection(intersections);
	}

	@Override
	public Vector3f getNormal(Point3f point) {
		for (Triangle triangle : this.triangles) {
			if (triangle.contains(point)) {
				return new Vector3f(triangle.getContainingPlane().normal);
			}
		}
		return null;
	}

	@Override
	public Point2f getUVCoordinates(Point3f point) {
		for (Triangle tr : this.triangles) {
			Point2f p1 = tr.getUVCoordinates(point);
			// triangle contains point?
			if (p1.x <= 1 && p1.y <= 1 && p1.x >= 0 && p1.y >= 0) {
				return p1;
			}
		}
		// this should not happen!!
		return new Point2f(0, 0);
	}

	@Override
	public int getShapeCount() {
		return triangles.size();
	}

	public List<Triangle> getTriangles() {
		return triangles;
	}

	@Override
	public void setShader(Shader shader) {

		for (Triangle triangle : triangles) {
			triangle.setShader(shader);
		}

		super.setShader(shader);
	}

	@Override
	public Point3f getUVWCoordinates(Point3f point) {
		for (Triangle tr : this.triangles) {
			Point3f p1 = tr.getUVWCoordinates(point);
			// triangle contains point?
			if (p1.x <= 1 && p1.y <= 1 && p1.z <= 1 && p1.x >= 0 && p1.y >= 0
					&& p1.z >= 0) {
				return p1;
			}
		}
		// this should not happen!!
		return new Point3f(0, 0, 0);
	}
}
